﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ExchangeHog.MessageParser
{
	public class Parser
	{
		class RecordingStreamReader : StreamReader
		{
			internal StreamWriter SWOutput { get; private set; }
			internal RecordingStreamReader(MemoryStream msInput, StreamWriter swOutput) : base(msInput) { SWOutput = swOutput; }

			public override int Read()
			{
				int r = base.Read();
				SWOutput.Write(Convert.ToChar(r));
				return r;
			}

			internal void ResetStreamWriter(StreamWriter sw)
			{
				SWOutput = sw;
			}
		}

		FieldNameLexer lexerFieldname;
		AddressLexer lexerStructuredfield;

		List<StructuredFieldToken> tokens;

		StructuredFieldToken NextToken()
		{
			StructuredFieldToken t;
			if (tokens.Count > 0)
			{
				t = tokens[0];
				tokens.RemoveAt(0);
			}
			else
				t = NextToken();

			return t;
		} // end of NextToken

		private void PutTokenBack(StructuredFieldToken t)
		{
			tokens.Insert(0, t);
		}

		//
		//		address     =  mailbox                      ; one addressee
		//	                 /  group                        ; named list
		//	
		//	    group       =  phrase ":" [#mailbox] ";"
		//	
		//	    mailbox     =  addr-spec                    ; simple address
		//	                /  phrase route-addr            ; name & addr-spec
		//	
		//	    route-addr  =  "<" [route] addr-spec ">"
		//	
		//	    route       =  1#("@" domain) ":"           ; path-relative
		//	
		//	    addr-spec   =  local-part "@" domain        ; global address
		//	
		//	    local-part  =  word *("." word)             ; uninterpreted
		//	                                                 ; case-preserved
		//	
		//	    domain      =  sub-domain *("." sub-domain)
		//	
		//	    sub-domain  =  domain-ref / domain-literal
		//	
		//	    domain-ref  =  atom                         ; symbolic reference
		//	
		//
		private StructuredFieldToken ParseWord(StructuredFieldToken t = null)
		{
			if (t == null)
				t = NextToken();
			if (t.TType == StructuredFieldToken.TokenType.ATOM || t.TType == StructuredFieldToken.TokenType.QUOTEDSTRING)
				return t;
			else
				throw new ApplicationException(
					String.Format(
						"Not what we expected: {0} instead of {1}", t.TType.ToString(), "word"
					)
				);
		}

		//
		//	address = mailbox / group
		//	address = 
		//		addr-spec / 
		//		( phrase route-addr ) / 
		//		group
		//	address = 
		//		( local-part "@" domain ) / 
		//		( word+ "<" [route] local-part "@" domain ">" ) / 
		//		phrase ":" [#mailbox] ";"
		//	address = 
		//		( word *("." word) "@" sub-domain *("." sub-domain) / 
		//		( word+ "<" [1#("@" domain ) ":"] word *("." word) "@" sub-domain *("." sub-domain) ">" ) /
		//		( word+ ":" [# mailbox ] ";" )
		//	address = 
		//		( word *("." word) "@" (atom / domain-literal) *("." (atom / domain-literal)) / 
		//		( word+ "<" [1#("@" domain ) ":"] word *("." word) "@" (atom / domain-literal) *("." (atom / domain-literal)) ">" ) /
		//		( word+ ":" [# mailbox ] ";" )
		//	word = atom / quoted-string
		//	address = 
		//		( word *("." word) "@" (atom / domain-literal) *("." (atom / domain-literal)) / 
		//		( word+ "<" routing-information ">" ) /
		//		( word+ ":" ... ";" )
		//
		private List<System.Net.Mail.MailAddress> ParseAddress() //RecordingStreamReader rsr, ref StreamWriter sw, ref MemoryStream ms)
		{
			bool isEmailAddressParsingDone = false;
			List<System.Net.Mail.MailAddress> emailaddresses = null;
			try
			{
				StructuredFieldToken tokenFirst = NextToken();
				if (tokenFirst.TType == StructuredFieldToken.TokenType.COMMA)
					tokenFirst = NextToken(); // skip delimiters

				var sbEmailAddress = new StringBuilder();
				while (!isEmailAddressParsingDone)
				{
					try
					{
						sbEmailAddress.Append(ParseWord(tokenFirst).Repr);
					}
					catch (ApplicationException ae)
					{
						PutTokenBack(tokenFirst);
						throw ae;
					}
					StructuredFieldToken nextafterword = NextToken();
					sbEmailAddress.Append(nextafterword.Repr);

					bool isAddrSpec = false;
					while (nextafterword.TType == StructuredFieldToken.TokenType.PERIOD)
					{
						sbEmailAddress.Append(ParseWord());
						nextafterword = NextToken();

						isAddrSpec = true;
					}
					if (isAddrSpec)
						if (nextafterword.TType != StructuredFieldToken.TokenType.AT)
							throw new ApplicationException(
								String.Format(
									"Not what we expected: {0} instead of {1}", nextafterword.TType.ToString(), "@"
								)
							);
					if (nextafterword.TType == StructuredFieldToken.TokenType.AT)
					{
						sbEmailAddress.Append(nextafterword.Repr);

						StructuredFieldToken subdomain = NextToken();
						if (subdomain.TType != StructuredFieldToken.TokenType.ATOM && subdomain.TType != StructuredFieldToken.TokenType.DOMAINLITERAL)
							throw new ApplicationException(
								String.Format(
									"Not what we expected: {0} instead of {1}", nextafterword.TType.ToString(), "atom or domain literal"
								)
							);
						sbEmailAddress.Append(subdomain.Repr);

						StructuredFieldToken nextaftersubdomain = NextToken();
						while (nextaftersubdomain.TType == StructuredFieldToken.TokenType.PERIOD)
						{
							sbEmailAddress.Append(nextaftersubdomain.Repr);

							subdomain = NextToken();
							if (subdomain.TType != StructuredFieldToken.TokenType.ATOM && subdomain.TType != StructuredFieldToken.TokenType.DOMAINLITERAL)
								throw new ApplicationException(
									String.Format(
										"Not what we expected: {0} instead of {1}", nextafterword.TType.ToString(), "atom or domain literal"
									)
								);
							sbEmailAddress.Append(subdomain.Repr);
							nextaftersubdomain = NextToken();
						}
						PutTokenBack(nextaftersubdomain);
						isEmailAddressParsingDone = true;
					}
					else if (nextafterword.TType == StructuredFieldToken.TokenType.ROUTINGINFORMATION)
					{
						sbEmailAddress.Append(nextafterword.Repr);
						isEmailAddressParsingDone = true;
					}
					else if (nextafterword.TType == StructuredFieldToken.TokenType.COLON)
					{
						//rsr.ResetStreamWriter(sw = new StreamWriter(ms = new MemoryStream()));

						//emailaddresses = new List<String>();
						while (true)
						{
							//List<String> grouped = ParseAddress(rsr, ref sw, ref ms);
							List<System.Net.Mail.MailAddress> grouped = ParseAddress();
							if (grouped != null)
								emailaddresses.AddRange(grouped);
							else
								break;
						}
						StructuredFieldToken semicolon = NextToken();
						if (semicolon.TType != StructuredFieldToken.TokenType.SEMICOLON)
							throw new ApplicationException(
								String.Format(
									"Not what we expected: {0} instead of {1}", semicolon.TType.ToString(), "semicolon"
								)
							);
						isEmailAddressParsingDone = true;
					}
				}
			}
			catch (ApplicationException ae)
			{
				//
				//	Ignore parsing errors
				//
				// System.Windows.Forms.MessageBox.Show("Parsing failed: " + ae, "MailMessageEx", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Exclamation);
			}

			//if (isEmailAddressParsingDone)
			//{
			//    if (emailaddresses == null)
			//    {
			//        sw.Flush();
			//        string emailaddress = Encoding.ASCII.GetString(ms.ToArray());
			//        emailaddress = emailaddress.Trim('\r', '\n', ' ', ',', '\t', ';');

			//        emailaddresses = new List<String>();
			//        emailaddresses.Add(emailaddress);
			//    }

			//    rsr.ResetStreamWriter(sw = new StreamWriter(ms = new MemoryStream()));
			//}
			return emailaddresses;
		} // end of ParseAddress

		private String ParseText(Stream s)
		{
			lexerStructuredfield = new AddressLexer();
			lexerStructuredfield.SetStream(s);

			StringBuilder sb = new StringBuilder();
			StructuredFieldToken t;
			do
			{
				t = lexerStructuredfield.NextToken();
				sb.Append(t.Repr);
			}
			while (t.TType == StructuredFieldToken.TokenType.ATOM || t.TType == StructuredFieldToken.TokenType.SPACE);

			return sb.ToString();
		} // end of ParseText

		private String ParseHeaderContentAsIs(Stream s)
		{
			var lexerDummy = new DummyHeaderContentsLexer();
			lexerDummy.SetStream(s);
			DummyHeaderContentsToken t = lexerDummy.NextToken();
			if (t.TType != DummyHeaderContentsToken.TokenType.HEADERCONTENTS)
				throw new ApplicationException(
					String.Format(
						"Not what we expected: {0} instead of {1}", t.TType.ToString(), DummyHeaderContentsToken.TokenType.HEADERCONTENTS.ToString()
					)
				);
			String contents = t.Repr;
			if (lexerDummy.NextToken().TType != DummyHeaderContentsToken.TokenType.CRLF)
				throw new ApplicationException(
					String.Format(
						"Not what we expected: {0} instead of {1}", t.TType.ToString(), DummyHeaderContentsToken.TokenType.HEADERCONTENTS.ToString()
					)
				);
			return contents;
		} // end of ParseHeaderContentAsIs

		// 
		//	content := "Content-Type" ":" type "/" subtype
		//	           *(";" parameter)
		//	           ; Matching of media type and subtype
		//	           ; is ALWAYS case-insensitive.
		//	
		//	type := discrete-type / composite-type
		//	
		//	discrete-type := "text" / "image" / "audio" / "video" /
		//	                 "application" / extension-token
		//	
		//	composite-type := "message" / "multipart" / extension-token
		//	
		//	extension-token := ietf-token / x-token
		//	
		//	ietf-token := <An extension token defined by a
		//	               standards-track RFC and registered
		//	               with IANA.>
		//	
		//	x-token := <The two characters "X-" or "x-" followed, with
		//	            no intervening white space, by any token>
		//	
		//	subtype := extension-token / iana-token
		//	
		//	iana-token := <A publicly-defined extension token. Tokens
		//	               of this form must be registered with IANA
		//	               as specified in RFC 2048.>
		//	
		//	parameter := attribute "=" value
		//	
		//	attribute := token
		//	             ; Matching of attributes
		//	             ; is ALWAYS case-insensitive.
		//	
		//	value := token / quoted-string
		//	
		//	token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
		//	            or tspecials>
		//	
		//	tspecials :=  "(" / ")" / "<" / ">" / "@" /
		//	              "," / ";" / ":" / "\" / <">
		//	              "/" / "[" / "]" / "?" / "="
		//	              ; Must be in quoted-string,
		//	              ; to use within parameter values
		//		

		class ContentType
		{
			public String Type { get; set; }
			public String Subtype { get; set; }

			List<Tuple<String, String>> parameters;
			public void AddParam(string name, string value) { parameters.Add(new Tuple<string, string>(name, value)); }
		}

		private String ParseContentType(Stream s, out ContentType ct)
		{
			ct = new ContentType();

			var sb = new StringBuilder();
			lexerStructuredfield = new AddressLexer();
			lexerStructuredfield.SetStream(s);

			StructuredFieldToken sft = lexerStructuredfield.ParseThis(StructuredFieldToken.TokenType.ATOM);
			sb.Append(sft.Repr); // type
			ct.Type = sft.Repr;
			sft = lexerStructuredfield.ParseThis(StructuredFieldToken.TokenType.SLASH);
			sb.Append(sft.Repr); // "/"
			sft = lexerStructuredfield.ParseThis(StructuredFieldToken.TokenType.ATOM);
			sb.Append(sft.Repr); // subtype
			ct.Subtype = sft.Repr;

			sft = lexerStructuredfield.NextNonWsToken();
			if (sft.TType == StructuredFieldToken.TokenType.SEMICOLON)
			{
				sb.Append(sft.Repr);
				var lexerContentTypeParams = new ContentTypeParamsLexer();
				lexerContentTypeParams.SetStream(s);

				ContentTypeParamsToken ctpt;
				do
				{
					ctpt = lexerContentTypeParams.ParseThis(ContentTypeParamsToken.TokenType.TOKEN);
					string name = ctpt.Repr;
					sb.Append(name);
					ctpt = lexerContentTypeParams.ParseThis(ContentTypeParamsToken.TokenType.EQUAL);
					sb.Append(ctpt.Repr); // "="
					ctpt = lexerContentTypeParams.NextNonWsToken();
					if (ctpt.TType == ContentTypeParamsToken.TokenType.TOKEN || ctpt.TType == ContentTypeParamsToken.TokenType.QUOTEDSTRING)
					{
						ct.AddParam(name, ctpt.Repr);
						sb.Append(ctpt.Repr);
					}
					else
						throw new ApplicationException(
							String.Format(
								"Not what we expected: {0} instead of {1} or {2}", ctpt.TType.ToString(), ContentTypeParamsToken.TokenType.TOKEN, ContentTypeParamsToken.TokenType.QUOTEDSTRING
							)
						);

					ctpt = lexerContentTypeParams.ParseThis(ContentTypeParamsToken.TokenType.SEMICOLON);
					sb.Append(ctpt.Repr); // ";"

					ctpt = lexerContentTypeParams.NextNonWsToken();
				} while (ctpt.TType == ContentTypeParamsToken.TokenType.SEMICOLON);
			}
			return sb.ToString();
		} // end of ParseContentType

		//
		//	from RFC 2183
		//
		//	disposition := "Content-Disposition" ":"
		//	               disposition-type
		//	               *(";" disposition-parm)
		//	
		//	disposition-type := "inline"
		//	                  / "attachment"
		//	                  / extension-token
		//	                  ; values are not case-sensitive
		//	
		//	disposition-parm := filename-parm
		//	                  / creation-date-parm
		//	                  / modification-date-parm
		//	                  / read-date-parm
		//	                  / size-parm
		//	                  / parameter
		//	
		//	filename-parm := "filename" "=" value
		//	
		//	creation-date-parm := "creation-date" "=" quoted-date-time
		//	
		//	modification-date-parm := "modification-date" "=" quoted-date-time
		//	
		//	read-date-parm := "read-date" "=" quoted-date-time
		//	
		//	size-parm := "size" "=" 1*DIGIT
		//	
		//	quoted-date-time := quoted-string
		//	                 ; contents MUST be an RFC 822 `date-time'
		//	                 ; numeric timezones (+HHMM or -HHMM) MUST be used
		//
		private String ParseContentDisposition(Stream s, out System.Net.Mime.ContentDisposition cd)
		{
			cd = new System.Net.Mime.ContentDisposition();

			var sb = new StringBuilder();
			lexerStructuredfield = new AddressLexer();
			lexerStructuredfield.SetStream(s);

			StructuredFieldToken sft = lexerStructuredfield.ParseThis(StructuredFieldToken.TokenType.ATOM);
			sb.Append(sft.Repr); // type
			cd.DispositionType = sft.Repr;
			cd.Inline = String.Compare(cd.DispositionType, "inline", true) == 0; // ignore case
			
			sft = lexerStructuredfield.NextNonWsToken();
			if (sft.TType == StructuredFieldToken.TokenType.SEMICOLON)
			{
				sb.Append(sft.Repr);
				var lexerContentTypeParams = new ContentTypeParamsLexer();
				lexerContentTypeParams.SetStream(s);

				ContentTypeParamsToken ctpt;
				do
				{
					ctpt = lexerContentTypeParams.ParseThis(ContentTypeParamsToken.TokenType.TOKEN);
					string name = ctpt.Repr;
					sb.Append(name);
					ctpt = lexerContentTypeParams.ParseThis(ContentTypeParamsToken.TokenType.EQUAL);
					sb.Append(ctpt.Repr); // "="
					ctpt = lexerContentTypeParams.NextNonWsToken();
					if (ctpt.TType == ContentTypeParamsToken.TokenType.TOKEN || ctpt.TType == ContentTypeParamsToken.TokenType.QUOTEDSTRING)
					{
						cd.Parameters.Add(name, ctpt.Repr);
						switch (name.ToLower())
						{
							case "creation-date": cd.CreationDate = DateTime.Parse(ctpt.Repr); break;
							case "filename": cd.FileName = ctpt.Repr; break;
							case "modification-date": cd.ModificationDate = DateTime.Parse(ctpt.Repr); break;
							case "read-date": cd.ReadDate = DateTime.Parse(ctpt.Repr); break;
							case "size": cd.Size = long.Parse(ctpt.Repr); break;
						}

						sb.Append(ctpt.Repr);
					}
					else
						throw new ApplicationException(
							String.Format(
								"Not what we expected: {0} instead of {1} or {2}", ctpt.TType.ToString(), ContentTypeParamsToken.TokenType.TOKEN, ContentTypeParamsToken.TokenType.QUOTEDSTRING
							)
						);

					ctpt = lexerContentTypeParams.ParseThis(ContentTypeParamsToken.TokenType.SEMICOLON);
					sb.Append(ctpt.Repr); // ";"

					ctpt = lexerContentTypeParams.NextNonWsToken();
				} while (ctpt.TType == ContentTypeParamsToken.TokenType.SEMICOLON);
			}
			return sb.ToString();
		} // end of ParseContentDisposition


		private Stream ParseMultipartBody(Stream s, Stack<string> boundaries)
		{
			ParseHeaders(s);

			MemoryStream ms = new MemoryStream();
			StreamWriter sw = new StreamWriter(ms, encoding);
			while (!NextLineStartsWithBoundary(s, boundaries))
				sw.Write(s.NextLine());


		} // end of ParseMultipartBody

		private void ParseContentHeaders(Stream s)
		{
			string cte = "";
			ContentType ct = null;
			System.Net.Mime.ContentDisposition cd = null;

			//
			//	Process headers
			//
			while (true)
			{
				FieldNameToken fnt = lexerFieldname.NextNonWsToken();
				if (fnt.TType == FieldNameToken.TokenType.CRLF)
					break;
				else if (fnt.TType != FieldNameToken.TokenType.FIELDNAME)
				{
					throw new ApplicationException(
						String.Format(
							"Not what we expected: {0} instead of {1} or {2}", fnt.TType.ToString(), FieldNameToken.TokenType.CRLF, FieldNameToken.TokenType.FIELDNAME
						)
					);
				}
				string headerName = fnt.Repr;
				lexerFieldname.ParseThis(FieldNameToken.TokenType.COLON);

				switch (headerName)
				{
					case "Content-Type":
						mm.Headers.Add(headerName, ParseContentType(s, out ct));
						break;
					case "Conent-Transfer-Encoding":
						//
						//	encoding := "Content-Transfer-Encoding" ":" mechanism
						//	
						//	mechanism := "7bit" / "8bit" / "binary" /
						//	             "quoted-printable" / "base64" /
						//	             ietf-token / x-token
						//
						cte = ParseHeaderContentAsIs(s);
						mm.Headers.Add(headerName, cte);
						break;
					case "Content-ID":
						//
						//	id := "Content-ID" ":" msg-id
						//
						mm.Headers.Add(headerName, ParseHeaderContentAsIs(s));
						break;
					case "Content-Description":
						//
						//	description := "Content-Description" ":" *text
						//
						mm.Headers.Add(headerName, ParseHeaderContentAsIs(s));
						break;

					case "Content-Disposition":
						//
						//	from RFC 2183
						//
						//	disposition := "Content-Disposition" ":"
						//	               disposition-type
						//	               *(";" disposition-parm)
						//	
						//	disposition-type := "inline"
						//	                  / "attachment"
						//	                  / extension-token
						//	                  ; values are not case-sensitive
						//	
						//	disposition-parm := filename-parm
						//	                  / creation-date-parm
						//	                  / modification-date-parm
						//	                  / read-date-parm
						//	                  / size-parm
						//	                  / parameter
						//	
						//	filename-parm := "filename" "=" value
						//	
						//	creation-date-parm := "creation-date" "=" quoted-date-time
						//	
						//	modification-date-parm := "modification-date" "=" quoted-date-time
						//	
						//	read-date-parm := "read-date" "=" quoted-date-time
						//	
						//	size-parm := "size" "=" 1*DIGIT
						//	
						//	quoted-date-time := quoted-string
						//	                 ; contents MUST be an RFC 822 `date-time'
						//	                 ; numeric timezones (+HHMM or -HHMM) MUST be used
						//
						mm.Headers.Add(headerName, ParseContentDisposition(s, out cd));
						break;
					default:
						mm.Headers.Add(headerName, ParseHeaderContentAsIs(s));
						break;
				}
			}
		}

		public System.Net.Mail.MailMessage ParseMessage(Stream s)
		{
			lexerFieldname = new FieldNameLexer();
			lexerFieldname.SetStream(s);
			
			tokens = new List<StructuredFieldToken>(); 

			System.Net.Mail.MailMessage mm = new System.Net.Mail.MailMessage();

			string cte = "";
			ContentType ct = null;
			System.Net.Mime.ContentDisposition cd = null;

			//
			//	Process headers
			//
			while(true)
			{
				FieldNameToken fnt = lexerFieldname.NextNonWsToken();
				if (fnt.TType == FieldNameToken.TokenType.CRLF)
					break;
				else if (fnt.TType != FieldNameToken.TokenType.FIELDNAME)
				{
					throw new ApplicationException(
						String.Format(
							"Not what we expected: {0} instead of {1} or {2}", fnt.TType.ToString(), FieldNameToken.TokenType.CRLF, FieldNameToken.TokenType.FIELDNAME
						)
					);
				}
				string headerName = fnt.Repr;
				lexerFieldname.ParseThis(FieldNameToken.TokenType.COLON);

				switch (headerName)
				{
					case "Date":
					case "Resent-Date":
					case "Return-path": mm.Headers.Add(headerName, ParseHeaderContentAsIs(s)); break;
					case "From": mm.From = ParseAddress()[0]; break;
					case "Sender": mm.Sender = ParseAddress()[0]; break;
					case "Reply-To": foreach (System.Net.Mail.MailAddress maReplyTo in ParseAddress()) mm.ReplyToList.Add(maReplyTo); break;
					case "Resent-From":
					case "Resent-Sender":
					case "Resent-Reply-To": mm.Headers.Add(headerName, ParseHeaderContentAsIs(s)); break;
					case "To": foreach (System.Net.Mail.MailAddress maTo in ParseAddress()) mm.To.Add(maTo); break;
					case "Resent-To": mm.Headers.Add(headerName, ParseHeaderContentAsIs(s)); break;
					case "cc": foreach (System.Net.Mail.MailAddress maCC in ParseAddress()) mm.CC.Add(maCC); break;
					case "Resent-cc": mm.Headers.Add(headerName, ParseHeaderContentAsIs(s)); break;
					case "bcc": foreach (System.Net.Mail.MailAddress maBCC in ParseAddress()) mm.Bcc.Add(maBCC); break;
					case "Resent-bcc": mm.Headers.Add(headerName, ParseHeaderContentAsIs(s)); break;
					case "Subject": mm.Subject = ParseText(s); break;
	
					//
					//	from RFC 2045
					//
					case "MIME-Version":
						var lexerMimeHeaderContents = new MimeHeaderContentsLexer();
						lexerMimeHeaderContents.SetStream(s);
						var token = lexerMimeHeaderContents.NextToken();
						if (token.TType == MimeHeaderContentsToken.TokenType.HEADERCONTENTS)
							if (token.Repr != "1.0")
								throw new ApplicationException("Expected 1.0 as a value for MIME-Version header. Instead got " + token.Repr);
						break;
					case "Content-Type":
						mm.Headers.Add(headerName, ParseContentType(s, out ct)); 
						break;
					case "Conent-Transfer-Encoding":
						//
						//	encoding := "Content-Transfer-Encoding" ":" mechanism
						//	
						//	mechanism := "7bit" / "8bit" / "binary" /
						//	             "quoted-printable" / "base64" /
						//	             ietf-token / x-token
						//
						cte = ParseHeaderContentAsIs(s);
						mm.Headers.Add(headerName, cte);
						break;
					case "Content-ID":
						//
						//	id := "Content-ID" ":" msg-id
						//
						mm.Headers.Add(headerName, ParseHeaderContentAsIs(s));
						break;
					case "Content-Description":
						//
						//	description := "Content-Description" ":" *text
						//
						mm.Headers.Add(headerName, ParseHeaderContentAsIs(s));
						break;
	
					case "Content-Disposition":
						//
						//	from RFC 2183
						//
						//	disposition := "Content-Disposition" ":"
						//	               disposition-type
						//	               *(";" disposition-parm)
						//	
						//	disposition-type := "inline"
						//	                  / "attachment"
						//	                  / extension-token
						//	                  ; values are not case-sensitive
						//	
						//	disposition-parm := filename-parm
						//	                  / creation-date-parm
						//	                  / modification-date-parm
						//	                  / read-date-parm
						//	                  / size-parm
						//	                  / parameter
						//	
						//	filename-parm := "filename" "=" value
						//	
						//	creation-date-parm := "creation-date" "=" quoted-date-time
						//	
						//	modification-date-parm := "modification-date" "=" quoted-date-time
						//	
						//	read-date-parm := "read-date" "=" quoted-date-time
						//	
						//	size-parm := "size" "=" 1*DIGIT
						//	
						//	quoted-date-time := quoted-string
						//	                 ; contents MUST be an RFC 822 `date-time'
						//	                 ; numeric timezones (+HHMM or -HHMM) MUST be used
						//
						mm.Headers.Add(headerName, ParseContentDisposition(s, out cd));
						break;
					default:
						mm.Headers.Add(headerName, ParseHeaderContentAsIs(s));
						break;
				}
			}

			//
			//	Process body
			//
			//
			//	Need to determine if we need to parse body as a multipart
			//
			if (ct == null)
				ct = new ContentType("text", "plain");

			if (String.Compare(ct.Type, "multipart", true) == 0)
			{
				//
				//
				//
				string boundary = ct.GetParam("boundary");
				boundaries.push(boundary);

				skipUntilBoundary(boundary); // skip preamble
				mm.AlternateViews.Add(new System.Net.Mail.AlternateView(ParseMultipartBody(s, boundaries)));

				if (lastboundary)
					break;
			}
			if (String.Compare(ct.Type, "message", true) == 0)
				//
				//
				//
				;
			else
			{
				string charset = ct.GetParam("charset");
				if (charset == null)
					charset = "us-ascii";
				mm.BodyEncoding = charset;

				if (cte == null)
					cte = "7bit";

				if (String.Compare(cte, "quoted-printable", true))
					mm.Body = ReadQutedPrintableBody(s);
				else if (String.Compare(cte, "base64", true))
					mm.Body = ReadBase64Body(s);
				else
					mm.Body = s.Read7bitBody(s, charset);
			}
			return mm;
		} // end of ParseMessage


//
//	message =  
//			1*(
//				"Date"        ":"   date-time		; Original
//				[ "Resent-Date" ":"   date-time ]	; Forwarded
//				[  "Return-path" ":" route-addr		; return address - path to sender
//					1*(
//						"Received"    ":"			; one per relay
//						["from" domain]				; sending host
//						["by"   domain]				; receiving host
//						["via"  atom]				; physical path
//						*("with" atom)				; link/mail protocol
//						["id"   msg-id]				; receiver msg id
//						["for"  addr-spec]			; initial form
//						";"    date-time			; time received
//					)
//				]									; net traversals
//				(
//					"From"       ":"   mailbox		; Single author
//					/ ( 
//						"Sender"     ":"   mailbox	; Actual submittor
//						"From"       ":" 1#mailbox)	; Multiple authors or not sender
//						)
//					[ "Reply-To"   ":" 1#address] 
//				)
//				[
//					(
//						"Resent-From"      ":"   mailbox
//						/ ( "Resent-Sender"    ":"   mailbox
//							"Resent-From"      ":" 1#mailbox  )
//					)
//					[ "Resent-Reply-To"  ":" 1#address]
//				]									; forwarded
//				1*(
//					"To"          ":" 1#address		; Primary
//					/  "Resent-To"   ":" 1#address
//					/  "cc"          ":" 1#address	; Secondary
//					/  "Resent-cc"   ":" 1#address
//					/  "bcc"         ":"  #address	; Blind carbon
//					/	"Resent-bcc"  ":"  #address
//				)
//				*(
//					"Message-ID"        ":"   msg-id
//					/	"Resent-Message-ID" ":"   msg-id
//					/	"In-Reply-To"       ":"  *(phrase / msg-id)
//					/	"References"        ":"  *(phrase / msg-id)
//					/	"Keywords"          ":"  #phrase
//					/	"Subject"           ":"  *text
//					/	"Comments"          ":"  *text
//					/	"Encrypted"         ":" 1#2word
//					/	extension-field              ; To be defined
//					/	user-defined-field           ; May be pre-empted
//				)
//			)
//			*( CRLF *text )       ; Everything after first null line is message body
//
//			msg-id      =  "<" addr-spec ">"		; Unique message id
//
//			date-time   = 
//				[ 
//					("Mon"  / "Tue" /  "Wed"  / "Thu" /  "Fri"  / "Sat" /  "Sun") "," 
//				] 
//				1*2DIGIT 
//				(
//					"Jan"  /  "Feb" /  "Mar"  /  
//					"Apr" /  "May"  /  "Jun" /  
//					"Jul"  /  "Aug" / "Sep"  /  
//					"Oct" /  "Nov"  /  "Dec"
//				)
//				2DIGIT
//				2DIGIT ":" 2DIGIT [":" 2DIGIT]	; 00:00:00 - 23:59:59
//				(
//					"UT"  / "GMT"					; Universal Time
//													; North American : UT
//					/  "EST" / "EDT"				;  Eastern:  - 5/ - 4
//					/  "CST" / "CDT"				;  Central:  - 6/ - 5
//					/  "MST" / "MDT"				;  Mountain: - 7/ - 6
//					/  "PST" / "PDT"				;  Pacific:  - 8/ - 7
//					/  1ALPHA						; Military: Z = UT;
//													;	A:-1; (J not used)
//													;	M:-12; N:+1; Y:+12
//					/ ( ("+" / "-") 4DIGIT )		; Local differential
//													;	hours+min. (HHMM)
//				)
//			extension-field =
//				<Any field which is defined in a document
//				published as a formal extension to this
//				specification; none will have names beginning
//				with the string "X-">
//
//			user-defined-field =
//				<Any field which has not been defined
//				in this specification or published as an
//				extension to this specification; names for
//				such fields must be unique and may be
//				pre-empted by published extensions>
	}
}
